/* 
 *	Copyright (C) 2009-2010 miaohua
 *	http://code.google.com/p/lrcshower/
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "StdAfx.h"
#include "Resource.h"
#include "BrowseFolderDlg.h"


#ifdef _DEBUG
# define new DEBUG_NEW
# undef THIS_FILE
  static char THIS_FILE[] = __FILE__;
#endif


#ifdef _DEBUG

# define ASSERT_NULL_OR_VALID(pOb) \
  {if (NULL!= pOb) ::AfxAssertValidObject(pOb, THIS_FILE, __LINE__);}

#else  // not _DEBUG

# define ASSERT_NULL_OR_VALID(pOb)

#endif // _DEBUG

CBrowseFolderDlg::CBrowseFolderDlg(const CString &StrHint  /*= CString()*/,
                 UINT Flags,             /*= 0U  */
                 bool IsRecurse,         /*= false */  
                 CWnd *ParentWnd,        /*= NULL*/
                 int RootFolder) :       /*= CSIDL_DESKTOP*/
                 m_Flags(Flags),
                 m_HintTip(StrHint),
                 m_ParentWnd(ParentWnd),
                 m_RootFolder(NULL),
                 m_IsRecurse(IsRecurse)
{
     ASSERT_NULL_OR_VALID(m_ParentWnd);

     m_RootFolder = ResolveCsidl(RootFolder);
}

CBrowseFolderDlg::CBrowseFolderDlg(int HintID,
                 UINT Flags,       /*= 0U*/
                 bool IsRecurse,   /*= false */  
                 CWnd *ParentWnd,  /*= NULL*/
                 int RootFolder) : /*= CSIDL_DESKTOP*/
                 m_Flags(Flags),
                 m_ParentWnd(ParentWnd),
                 m_RootFolder(NULL),
                 m_IsRecurse(IsRecurse)
{
     ASSERT_NULL_OR_VALID(m_ParentWnd); // Paranoia

     if( !m_HintTip.LoadString(HintID) )
         AfxThrowResourceException();

     m_RootFolder = ResolveCsidl(RootFolder);
}

 CBrowseFolderDlg::CBrowseFolderDlg(const CString &StrHint,
                  const CString &StrRoot,
                  UINT Flags,        /*= 0U*/
                  bool IsRecurse,    /*= false */  
                  CWnd *ParentWnd) : /*= NULL*/
                  m_Flags(Flags),
                  m_HintTip(StrHint),
                  m_ParentWnd(ParentWnd),
                  m_RootFolder(NULL),
                  m_IsRecurse(IsRecurse)
{
     ASSERT_NULL_OR_VALID(m_ParentWnd);

     m_RootFolder = ResolveFsPath(StrRoot);
}


CBrowseFolderDlg::CBrowseFolderDlg(int nHint,
                 const CString &StrRoot,
                 UINT Flags,        /*= 0U*/
                 bool IsRecurse,    /*= false */  
                 CWnd *ParentWnd) : /*= NULL*/
                 m_Flags(Flags),
                 m_ParentWnd(ParentWnd),
                 m_RootFolder(NULL),
                 m_IsRecurse(IsRecurse)
{
     ASSERT_NULL_OR_VALID(m_ParentWnd); 

     if( !m_HintTip.LoadString(nHint) )
         AfxThrowResourceException();

     m_RootFolder = ResolveFsPath(StrRoot);
}

CBrowseFolderDlg::~CBrowseFolderDlg()
{
     HRESULT HR = S_OK;
     IMalloc *pMalloc = NULL;
     if( FAILED(HR = SHGetMalloc(&pMalloc)) )
         AfxThrowOleException(HR);
     pMalloc->Free(m_RootFolder);
     pMalloc->Release();
}

bool CBrowseFolderDlg::GetRecurse() const
{
     return m_IsRecurse == TRUE;
}

CString CBrowseFolderDlg::GetDisplayName() const
{
     return m_DisplayName;
}

CString CBrowseFolderDlg::GetPath() const
{
     return m_Path;
}

void CBrowseFolderDlg::OnInitBFFDialog()
{
     static const UINT BUTTON_STYLE = BS_CHECKBOX | WS_CHILD |
                                      WS_VISIBLE | WS_TABSTOP;

     // Load check box caption
     CString ChkCaption;
     ChkCaption.LoadString(IDS_RECURSE_CHK_TITLE);
     // Get caption size
     CSize CaptionSize;
     GetTextExtentPoint32(GetDC()->m_hDC, ChkCaption, ChkCaption.GetLength(), &CaptionSize);
     // The `cy' is for the actual check itself
     int CheckboxWidth = CaptionSize.cx + CaptionSize.cy;

     // Now, figure out how to position the thing ...
     // RectDlg is the client area of the entire Window ...
     RECT RectDlg;
     GetClientRect(&RectDlg);   
     // Get "OK" button's area
     RECT RectOK;
     CButton *BtnOK = (CButton*)(GetDlgItem(IDOK));
     BtnOK->GetClientRect(&RectOK);    // And rcOk that of just the OK button.
 
     // "Recurse" button' position
     CRect RectRecurse;
     RectRecurse.left = RectDlg.left+12;
     RectRecurse.right = RectRecurse.left + CheckboxWidth;
     RectRecurse.bottom = RectDlg.bottom - 10;
     RectRecurse.top = RectDlg.bottom - (RectOK.bottom-RectOK.top)-10;

     if( !m_RecurseChkBox.Create(ChkCaption, BUTTON_STYLE, RectRecurse, this, IDC_CHK_RECURSE) )
         AfxThrowOleException(HRESULT_FROM_WIN32(::GetLastError()));

     m_RecurseChkBox.SetCheck(m_IsRecurse ? BST_CHECKED:BST_UNCHECKED);   

     // For reasons
     // unclear to me, it uses a horrible font.  Let's change that:
     CFont *pFont = GetFont();
     m_RecurseChkBox.SetFont(pFont, TRUE);

     UpdateData(FALSE);
}

BOOL CBrowseFolderDlg::OnCmdMsg(UINT nID,
                                int nCode,
                                void* pExtra,
                                AFX_CMDHANDLERINFO* pHandlerInfo)
{
     if( IDC_CHK_RECURSE == nID && nCode == CN_COMMAND )
     {
         m_IsRecurse = ! m_IsRecurse;
         m_RecurseChkBox.SetCheck(m_IsRecurse ? 1 : 0);
         return TRUE;
     }

     return CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

void CBrowseFolderDlg::DoDataExchange(CDataExchange* pDX)
{
     CWnd::DoDataExchange(pDX);
     DDX_Check(pDX, IDC_CHK_RECURSE, m_IsRecurse);
}



void CBrowseFolderDlg::OnDestroy()
{
     UpdateData(TRUE);
     CWnd::OnDestroy();
}

int CBrowseFolderDlg::DoModal()
{
     // Fill out a 'BROWSEINFO' structure to hand to 'SHBrowseFor-Folder'
     BROWSEINFO BrowseInfo;
     ZeroMemory(&BrowseInfo, sizeof(BROWSEINFO));
     BrowseInfo.hwndOwner = (NULL == m_ParentWnd ? NULL : m_ParentWnd->m_hWnd);
     BrowseInfo.pidlRoot = m_RootFolder;
     // Use a CString for memory management
     BrowseInfo.pszDisplayName = m_DisplayName.GetBufferSetLength(MAX_PATH);
     BrowseInfo.lpszTitle = m_HintTip;
     BrowseInfo.ulFlags = m_Flags;
     BrowseInfo.lpfn = BrowseCallbackProc;
     BrowseInfo.lParam = (long)this;

     LPITEMIDLIST ItemIdList = NULL;
     if( (ItemIdList = SHBrowseForFolder(&BrowseInfo)) == NULL )
     {
          // User Cancelled out, do clean up 
          m_DisplayName.ReleaseBuffer();
          return IDCANCEL;
     }

     // Get user selected path
     SHGetPathFromIDList(ItemIdList, m_Path.GetBufferSetLength(MAX_PATH));

     // Cleanup time ...
     m_Path.ReleaseBuffer();
     m_DisplayName.ReleaseBuffer();

     IMalloc * pMalloc = NULL;
     HRESULT HR = S_OK;
     if( FAILED(HR = SHGetMalloc(&pMalloc)) )
         AfxThrowOleException(HR);
     pMalloc->Free(ItemIdList);
     pMalloc->Release();

     return IDOK;
}

void CBrowseFolderDlg::EnableOk(BOOL bEnable)
{
     ASSERT(NULL != m_hWnd);
     SendMessage(BFFM_ENABLEOK, 0U, bEnable ? 1L : 0L);
}

void CBrowseFolderDlg::SetSelection(LPCITEMIDLIST pItemIdList)
{
     ASSERT(NULL != m_hWnd);
     ASSERT_POINTER(pItemIdList, ITEMIDLIST);
     SendMessage(BFFM_SETSELECTION, (WPARAM)FALSE, (LPARAM)pItemIdList);
}

void CBrowseFolderDlg::SetSelection(const CString &Path)
{
     ASSERT(NULL != m_hWnd);
     SendMessage(BFFM_SETSELECTION, (WPARAM)TRUE, (LPARAM)(LPCTSTR)Path);
}

void CBrowseFolderDlg::SetText(const CString &strNewText)
{
     ASSERT(NULL != m_hWnd);
     SendMessage(BFFM_SETSTATUSTEXT, 0U, reinterpret_cast<LPARAM>(static_cast<LPCTSTR>(strNewText)));
}

void CBrowseFolderDlg::OnBFFSelChanged(LPITEMIDLIST /*pNewSel*/)
{ /* No handling by default */ }

BOOL CBrowseFolderDlg::OnBFFValidateFailed(const CString & /*strBadName*/)
{ return TRUE; /* No handling by default */ }




BEGIN_MESSAGE_MAP(CBrowseFolderDlg, CWnd)
     //{{AFX_MSG_MAP(CBrowseFolderDlg)
     // NOTE - the ClassWizard will add and remove mapping macros here.
     ON_WM_DESTROY()
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()


int CBrowseFolderDlg::BrowseCallbackProc(HWND hWnd,
                                UINT nMsg,
                                LPARAM lParam,
                                LPARAM lpData)
{
     CBrowseFolderDlg *pWnd = reinterpret_cast<CBrowseFolderDlg*>(lpData);
     ASSERT_VALID(pWnd);
     ASSERT(NULL == pWnd->m_hWnd || hWnd == pWnd->m_hWnd);

     if( NULL == pWnd->m_hWnd && !pWnd->SubclassWindow(hWnd) )
         AfxThrowOleException(HRESULT_FROM_WIN32(::GetLastError()));

     switch (nMsg)
     {
          case BFFM_INITIALIZED:
               // Indicates the browse dialog box has finished
               // initializing. The lParam value is zero.
               pWnd->OnInitBFFDialog();
               return 0;
          case BFFM_SELCHANGED:
               // Indicates the selection has changed. The lParam parameter
               // points to the item identifier list for the newly selected
               // item.
               {
                LPITEMIDLIST p = reinterpret_cast<LPITEMIDLIST>(lParam);
                ASSERT_POINTER(p, ITEMIDLIST);
                pWnd->OnBFFSelChanged(p);
                return 0;
               }
          case BFFM_VALIDATEFAILED:
               // Indicates the user typed an invalid name into the edit box
               // of the browse dialog box. The lParam parameter is the
               // address of a character buffer that contains the invalid
               // name. An application can use this message to inform the
               // user that the name entered was not valid. Return zero to
               // allow the dialog to be dismissed or nonzero to keep the
               // dialog displayed.
               {
                LPTSTR p = reinterpret_cast<LPTSTR>(lParam);
                ASSERT(!::IsBadStringPtr(p, UINT_MAX));
                BOOL bDismissOk = pWnd->OnBFFValidateFailed(CString(p));
                return bDismissOk ? 0 : 1;
               }
          default:
               TRACE(_T("WARNING: Unknown message 0x%08x (0x%08x) ")
                     _T("passed to CYABFFW::BrowseCallbackProc!"), 
                     nMsg, lParam);
               return 0;
     }
}

LPITEMIDLIST CBrowseFolderDlg::ResolveCsidl(int nCsidl) const
{
     // Short-circuit special case ...
     if( CSIDL_DESKTOP == nCsidl )
         return NULL;

     LPITEMIDLIST PidlRoot = NULL;
     HRESULT hr = SHGetFolderLocation(NULL, nCsidl, NULL, 0U, &PidlRoot);
     if( FAILED(hr) )
     {
         ASSERT(NULL == PidlRoot);
         AfxThrowOleException(hr);
     }

     return PidlRoot;              // Caller assumes responsibility
}

LPITEMIDLIST CBrowseFolderDlg::ResolveFsPath(const CString &StrPath) const
{
     USES_CONVERSION;
# ifdef _DEBUG
     // Check attributes
     DWORD FileAttrs = GetFileAttributes(StrPath);
     ASSERT(0xffffffff != FileAttrs && FILE_ATTRIBUTE_DIRECTORY & FileAttrs);
# endif  // _DEBUG

     HRESULT HR = S_OK;
     IShellFolder *pDesktop = NULL;
     if( FAILED(HR = SHGetDesktopFolder(&pDesktop)) )
         AfxThrowOleException(HR);

     // Unfortunately, T2OLE expects a non-const string, so ...
     LPOLESTR StrPath2 = T2OLE(const_cast<LPTSTR>(static_cast<LPCTSTR>(StrPath)));
     LPITEMIDLIST pItemIdList = NULL;
     if( FAILED(HR = pDesktop->ParseDisplayName(NULL, NULL,
                                                StrPath2, NULL,
                                                &pItemIdList, NULL)) )
     {
         pDesktop->Release();
         AfxThrowOleException(HR);
     }

     pDesktop->Release();
     return pItemIdList;           // Caller assumes responsibility
}
